﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="IterativeSlaeSolver{T}.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra
{
    #region Usings

    using System;
    using System.Collections.Generic;

    using ExulLibrary.Exceptions;
    using ExulLibrary.Mathematics.LinearAlgebra.Exceptions;

    #endregion

    /// <summary>
    ///     Represents an iterative solver of a system of linear algebraic equations.
    /// </summary>
    /// <typeparam name="T">
    ///     The type of value of elements.
    /// </typeparam>
    public abstract class IterativeSlaeSolver<T> : SlaeSolver<T>
    {
        #region Properties

        #region public

        /// <summary>
        ///     Gets a value indicating whether this instance is initialized.
        /// </summary>
        /// <value>
        ///     <see langword="true"/> if this instance is initialized; otherwise, <see langword="false"/>.
        /// </value>
        public bool IsInitialized
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the stop criterion.
        /// </summary>
        /// <value>
        ///     The stop criterion.
        /// </value>
        public IterationsStopCriterion<T> StopCriterion
        {
            get;
            private set;
        }

        /// <summary>
        ///     Gets the size of the target system to solve.
        /// </summary>
        /// <value>
        ///     The size of the target system to solve.
        /// </value>
        public int TargetSize
        {
            get;
            private set;
        }

        #endregion

        #endregion

        #region Overridden Methods

        #region protected

        /// <summary>
        ///     Solves the specified system.
        /// </summary>
        /// <param name="system">
        ///     The system to solve.
        /// </param>
        /// <param name="solution">
        ///     The vector to store the solution.
        /// </param>
        protected override sealed void SolveSystem(Slae<T> system, DenseVector<T> solution)
        {
            this.SolveSystem(system, solution, null);
        }

        #endregion

        #endregion

        #region Methods

        #region public

        /// <summary>
        ///     Initializes the solver.
        /// </summary>
        /// <param name="targetSize">
        ///     The size of the target system to solve.
        /// </param>
        /// <param name="stopCriterion">
        ///     The stop criterion.
        /// </param>
        /// <param name="parameters">
        ///     The key-value collection of additional parameters for the solver.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="stopCriterion"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NegativeOrZeroSizeException">
        ///     <paramref name="targetSize"/> is negative or equal to zero.
        /// </exception>
        /// <exception cref="NotFoundSolverParameterException">
        ///     <paramref name="parameters"/> doesn't contain all required parameters.
        /// </exception>
        public virtual void Initialize(
            int targetSize, IterationsStopCriterion<T> stopCriterion, Dictionary<string, object> parameters)
        {
            #if SAFE

            if (targetSize <= 0)
            {
                throw new NegativeOrZeroSizeException("targetSize", targetSize);
            }

            if (stopCriterion == null)
            {
                throw new ArgumentNullException("stopCriterion");
            }

            #endif

            this.TargetSize = targetSize;
            this.StopCriterion = stopCriterion;
            this.IsInitialized = true;
        }

        /// <summary>
        ///     Solves the specified system.
        /// </summary>
        /// <param name="system">
        ///     The system to solve.
        /// </param>
        /// <param name="solution">
        ///     The vector to store the solution.
        /// </param>
        /// <param name="initialSolution">
        ///     The initial solution.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="system"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="solution"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="system"/> has size incompatible with the initialized target size.
        /// </exception>
        /// <exception cref="IncompatibleMatrixVectorException">
        ///     <paramref name="solution"/> is not <see langword="null"/> and
        ///     has length incompatible with <paramref name="system"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="initialSolution"/> is not <see langword="null"/> and
        ///         has length incompatible with <paramref name="system"/>.
        ///     </para>
        /// </exception>
        public void Solve(Slae<T> system, DenseVector<T> solution, DenseVector<T> initialSolution)
        {
            this.Solve(system, solution, initialSolution, null);
        }

        /// <summary>
        ///     Solves the specified system.
        /// </summary>
        /// <param name="system">
        ///     The system to solve.
        /// </param>
        /// <param name="solution">
        ///     The vector to store the solution.
        /// </param>
        /// <param name="preconditioningMatrix">
        ///     The preconditioning matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="system"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="solution"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="system"/> has size incompatible with the initialized target size.
        /// </exception>
        /// <exception cref="IncompatibleMatrixMatrixException">
        ///     <paramref name="preconditioningMatrix"/> is not <see langword="null"/> and
        ///     has size incompatible with <paramref name="system"/>.
        /// </exception>
        /// <exception cref="IncompatibleMatrixVectorException">
        ///     <paramref name="solution"/> is not <see langword="null"/> and
        ///     has length incompatible with <paramref name="system"/>.
        /// </exception>
        public void Solve(Slae<T> system, DenseVector<T> solution, DecompositionMatrix<T> preconditioningMatrix)
        {
            this.Solve(system, solution, null, preconditioningMatrix);
        }

        /// <summary>
        ///     Solves the specified system.
        /// </summary>
        /// <param name="system">
        ///     The system to solve.
        /// </param>
        /// <param name="solution">
        ///     The vector to store the solution.
        /// </param>
        /// <param name="initialSolution">
        ///     The initial solution.
        /// </param>
        /// <param name="preconditioningMatrix">
        ///     The preconditioning matrix.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="system"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="solution"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="system"/> has size incompatible with the initialized target size.
        /// </exception>
        /// <exception cref="IncompatibleMatrixVectorException">
        ///     <paramref name="solution"/> is not <see langword="null"/> and
        ///     has length incompatible with <paramref name="system"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="initialSolution"/> is not <see langword="null"/> and
        ///         has length incompatible with <paramref name="system"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="IncompatibleMatrixMatrixException">
        ///     <paramref name="preconditioningMatrix"/> is not <see langword="null"/> and
        ///     has size incompatible with <paramref name="system"/>.
        /// </exception>
        public void Solve(
            Slae<T> system, DenseVector<T> solution, DenseVector<T> initialSolution, DecompositionMatrix<T> preconditioningMatrix)
        {
            #if SAFE

            if (system == null)
            {
                throw new ArgumentNullException("system");
            }

            if (solution == null)
            {
                throw new ArgumentNullException("solution");
            }

            if (solution.Length < system.Size)
            {
                throw new IncompatibleMatrixVectorException(
                    "system", system.Size, system.Size, "solution", solution.Length, system.Size);
            }

            if ((initialSolution != null) && (initialSolution.Length < system.Size))
            {
                throw new IncompatibleMatrixVectorException(
                    "system", system.Size, system.Size, "initialSolution", initialSolution.Length, system.Size);
            }

            if ((preconditioningMatrix != null) && (preconditioningMatrix.Matrix.RowsCount != system.Size))
            {
                throw new IncompatibleMatrixMatrixException(
                    "system",
                    system.Size,
                    system.Size,
                    "preconditioningMatrix",
                    preconditioningMatrix.Matrix.RowsCount,
                    preconditioningMatrix.Matrix.ColumnsCount,
                    system.Size,
                    system.Size);
            }

            #endif

            if (preconditioningMatrix == null)
            {
                this.SolveSystem(system, solution, initialSolution);
            }
            else
            {
                this.SolveSystem(system, solution, preconditioningMatrix, initialSolution);
            }
        }

        #endregion

        #region protected

        /// <summary>
        ///     Solves the specified system without preconditioning.
        /// </summary>
        /// <param name="system">
        ///     The system to solve.
        /// </param>
        /// <param name="solution">
        ///     The vector to store the solution.
        /// </param>
        /// <param name="initialSolution">
        ///     The initial solution.
        /// </param>
        protected abstract void SolveSystem(Slae<T> system, DenseVector<T> solution, DenseVector<T> initialSolution);

        /// <summary>
        ///     Solves the specified system with preconditioning.
        /// </summary>
        /// <param name="system">
        ///     The system to solve.
        /// </param>
        /// <param name="solution">
        ///     The vector to store the solution.
        /// </param>
        /// <param name="preconditioningMatrix">
        ///     The preconditioning matrix.
        /// </param>
        /// <param name="initialSolution">
        ///     The initial solution.
        /// </param>
        protected abstract void SolveSystem(
            Slae<T> system,
            DenseVector<T> solution,
            DecompositionMatrix<T> preconditioningMatrix,
            DenseVector<T> initialSolution);

        #endregion

        #endregion
    }
}